package com.demo.oauth2jFinalShiroServer.web;

import java.io.IOException;
import java.util.Date;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.apache.oltu.oauth2.as.issuer.MD5Generator;
import org.apache.oltu.oauth2.as.issuer.OAuthIssuer;
import org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl;
import org.apache.oltu.oauth2.as.request.OAuthAuthzRequest;
import org.apache.oltu.oauth2.as.request.OAuthTokenRequest;
import org.apache.oltu.oauth2.as.response.OAuthASResponse;
import org.apache.oltu.oauth2.common.OAuth;
import org.apache.oltu.oauth2.common.error.OAuthError;
import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.apache.oltu.oauth2.common.message.OAuthResponse;
import org.apache.oltu.oauth2.common.message.types.GrantType;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.CollectionUtils;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;

import com.demo.oauth2jFinalShiroServer.cache.ClientCache;
import com.demo.oauth2jFinalShiroServer.cache.OAuth2EntityCache;
import com.demo.oauth2jFinalShiroServer.model.Client;
import com.demo.oauth2jFinalShiroServer.model.OAuth2Entity;
import com.jfinal.core.ActionKey;
import com.jfinal.core.Controller;

public class OAuth2Controller extends Controller {
	
	private static final Long EXPIRE_IN = 3600L;	//token过期时间(秒)
	
	@ActionKey("authorize")
	public void authorize() throws OAuthSystemException{
		HttpServletRequest req = getRequest();
		OAuthAuthzRequest oauthRequest = null;
		String clientId = null;
		String username = null;
		try {
			// 构建OAuth 授权请求
			oauthRequest = new OAuthAuthzRequest(req);
			// 检查传入的客户端id是否正确
			clientId = oauthRequest.getClientId();
			if (!ClientCache.CLIENT_INFO_MAP.containsKey(clientId)) {
				throw OAuthProblemException.error(OAuthError.CodeResponse.UNAUTHORIZED_CLIENT, "客户端验证失败，错误的client_id");
			}
			// 检查传入的redirectURI是否正确
//			validateRedirectionURI(oauthRequest.getRedirectURI(), ClientCache.CLIENT_INFO_MAP.get(clientId));
		} catch (OAuthSystemException | OAuthProblemException e) {
			setAttr(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME, e.getMessage());
			toLoginPage(oauthRequest);
			return;
		} 
		Subject subject = SecurityUtils.getSubject();
		if (!subject.isAuthenticated()) {
			//未认证进入登录页面进行登录和授权
			if (req.getMethod().equalsIgnoreCase("GET")) {
				//请求登录页面
				toLoginPage(oauthRequest);
				return;
			}else {
				//登录请求
				username = getPara("username");
				String password = getPara("password");
				UsernamePasswordToken token = new UsernamePasswordToken(username, password);
				try {
					subject.login(token);
				} catch (AuthenticationException e) {
					//登录失败
					if (e instanceof UnknownAccountException || e instanceof IncorrectCredentialsException) {
						setAttr(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME, "用户名/密码错误");
					}else {
						setAttr(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME, "其他错误");
					}
					toLoginPage(oauthRequest);
					return;
				}					
			}
		}else if(CollectionUtils.isEmpty(oauthRequest.getScopes())){
			//已认证但是未scope授权的用户
			//请求登录页面
			toLoginPage(oauthRequest);
			return;			
		}else{
			username = (String)subject.getPrincipal();
		}
		//已登录 和 登录成功
		OAuth2Entity entity = new OAuth2Entity();
		entity.setUsername(username);
		entity.setClientId(clientId);
		entity.setScopes(oauthRequest.getScopes());
		OAuthIssuer oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());
		String authorizationCode = oauthIssuerImpl.authorizationCode();
		entity.setCode(authorizationCode);
		OAuth2EntityCache.CODE_OAUTH2_MAP.put(authorizationCode, entity);
		
		//build OAuth response
		OAuthResponse resp = OAuthASResponse
			.authorizationResponse(req, HttpServletResponse.SC_FOUND)
			.setCode(authorizationCode)
			.location(oauthRequest.getRedirectURI())
			.buildQueryMessage();
		redirect(resp.getLocationUri());
	}

	private void validateRedirectionURI(String redirectURI, Client client) throws OAuthProblemException {
		String callbackUrl = client.getCallbackUrl();
		callbackUrl = callbackUrl.substring(0, callbackUrl.indexOf("/"));
		if (StringUtils.isEmpty(redirectURI) || redirectURI.indexOf(callbackUrl) == -1) {
			throw OAuthProblemException.error(OAuthError.CodeResponse.ACCESS_DENIED, "错误的callbackUrl");
		}
	}

	private void toLoginPage(OAuthAuthzRequest oauthRequest) {
		setAttr(OAuth.OAUTH_RESPONSE_TYPE, oauthRequest.getResponseType());
		setAttr(OAuth.OAUTH_CLIENT_ID, oauthRequest.getClientId());
		setAttr(OAuth.OAUTH_REDIRECT_URI, oauthRequest.getRedirectURI());
		setAttr(OAuth.OAUTH_STATE, oauthRequest.getState());
		renderJsp("/oauth2login.jsp");
	}
	
	@ActionKey("accessToken")
	public void accessToken() throws OAuthSystemException, IOException {
		HttpServletRequest request = getRequest();
		try {
			// 构建OAuth请求
			OAuthTokenRequest oauthRequest = new OAuthTokenRequest(request);
			String state = oauthRequest.getParam(OAuth.OAUTH_STATE);
			String authCode = oauthRequest.getCode();
			String clientId = oauthRequest.getClientId();
			
			// 检查传入的客户端id是否正确
			if (!ClientCache.CLIENT_INFO_MAP.containsKey(clientId)) {
				OAuthResponse oAuthResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST)
						.setError(OAuthError.TokenResponse.INVALID_CLIENT)
						.setErrorDescription("客户端验证失败，如错误的client_id/client_secret")
						.setState(state)
						.buildJSONMessage();
				renderText(oAuthResponse.getBody());
				return;
			}

			// 检查传入的客户端安全KEY是否正确
			if (!oauthRequest.getClientSecret().equals(ClientCache.CLIENT_INFO_MAP.get(clientId).getClientSecret())) {
				OAuthResponse oAuthResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
						.setError(OAuthError.TokenResponse.UNAUTHORIZED_CLIENT)
						.setErrorDescription("客户端验证失败，如错误的client_id/client_secret")
						.setState(state)
						.buildJSONMessage();
				renderText(oAuthResponse.getBody());
				return;
			}
			
			//检查授权码code是否正确，和客户端id是否匹配
			if (StringUtils.isEmpty(authCode)) {
				OAuthResponse oAuthResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
						.setError(OAuthError.TokenResponse.UNAUTHORIZED_CLIENT)
						.setErrorDescription("授权码验证失败，如错误的code或者与client_id不匹配")
						.setState(state)
						.buildJSONMessage();
				renderText(oAuthResponse.getBody());
				return;				
			}
			OAuth2Entity entity = OAuth2EntityCache.CODE_OAUTH2_MAP.get(authCode);
			if(entity == null || !clientId.equals(entity.getClientId()) || entity.isCodeExpired()){
				if (entity != null && entity.isCodeExpired()) {
					OAuth2EntityCache.CODE_OAUTH2_MAP.remove(authCode);
				}
				OAuthResponse oAuthResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
						.setError(OAuthError.TokenResponse.UNAUTHORIZED_CLIENT)
						.setErrorDescription("授权码验证失败，如错误的code或者与client_id不匹配")
						.setState(state)
						.buildJSONMessage();
				renderText(oAuthResponse.getBody());
				return;						
			}
			
			// 检查授权类型grant_type，此处只检查AUTHORIZATION_CODE类型，其他的还有PASSWORD或REFRESH_TOKEN
			if (!oauthRequest.getGrantType().equals(GrantType.AUTHORIZATION_CODE.toString())) {
				OAuthResponse oAuthResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST)
						.setError(OAuthError.TokenResponse.INVALID_GRANT)
						.setErrorDescription("错误的授权码")
						.setState(state)
						.buildJSONMessage();
				renderText(oAuthResponse.getBody());
				return;
			}

			// 生成Access Token
			OAuthIssuer oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());
			final String accessToken = oauthIssuerImpl.accessToken();
			entity.setAccessToken(accessToken);
			entity.setExpireIn(EXPIRE_IN);
			entity.setTokenStartTime(new Date());
			OAuth2EntityCache.TOKEN_OAUTH2_MAP.put(accessToken, entity);
			String username = entity.getUsername();
			OAuth2EntityCache.USER_TOKEN_MAP.put(username, accessToken);
			// 生成OAuth响应
			OAuthResponse oAuthResponse = OAuthASResponse.tokenResponse(HttpServletResponse.SC_OK)
					.setAccessToken(accessToken)
					.setExpiresIn(String.valueOf(EXPIRE_IN)) // 默认token60分钟过期
					.setParam(OAuth.OAUTH_STATE, state)
					.buildJSONMessage();
			renderText(oAuthResponse.getBody());
			return;
		} catch (OAuthProblemException e) {
			// 构建错误响应
			OAuthResponse oAuthResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST)
					.error(e)
					.buildJSONMessage();
			renderText(oAuthResponse.getBody());
			return;
		}
	}

	
}
